From 4449344fadecb5249f36ea1380a275eb68393442 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 18 Jun 2023 18:56:14 -0400 Subject: [PATCH] a11y: Reimplement name computation Reimplement the name computation to follow the spec (https://www.w3.org/TR/accname-1.2) more closely. Also, unify the functions for name and description, since their only difference is which property/relation they use. --- gtk/gtkatcontext.c | 407 ++++++++++++++++++++------------------------- 1 file changed, 184 insertions(+), 223 deletions(-) diff --git a/gtk/gtkatcontext.c b/gtk/gtkatcontext.c index bdd1597ae2..6e1703f9b5 100644 --- a/gtk/gtkatcontext.c +++ b/gtk/gtkatcontext.c @@ -1007,10 +1007,6 @@ gtk_at_context_get_accessible_relation (GtkATContext *self, return gtk_accessible_attribute_set_get_value (self->relations, relation); } -/* See the WAI-ARIA § 4.3, "Accessible Name and Description Computation", - * and https://www.w3.org/TR/accname-1.2/ - */ - /* See ARIA 5.2.8.4, 5.2.8.5 and 5.2.8.6 for the prohibited, from author * and from content parts, and the table in * https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/ @@ -1147,124 +1143,158 @@ gtk_accessible_role_get_naming (GtkAccessibleRole role) return (GtkAccessibleNaming) (naming[role] & ~(NAME_FROM_AUTHOR|NAME_FROM_CONTENT)); } -static void -gtk_at_context_get_name_accumulate (GtkATContext *self, - GPtrArray *names, - gboolean recurse) +static gboolean +is_nested_button (GtkATContext *self) { - GtkAccessibleValue *value = NULL; + GtkAccessible *accessible; + GtkWidget *widget, *parent; - if (gtk_accessible_attribute_set_contains (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL)) - { - value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL); + accessible = gtk_at_context_get_accessible (self); - g_ptr_array_add (names, (char *) gtk_string_accessible_value_get (value)); - } + if (!GTK_IS_WIDGET (accessible)) + return FALSE; - if (recurse && gtk_accessible_attribute_set_contains (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY)) - { - value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY); + widget = GTK_WIDGET (accessible); + parent = gtk_widget_get_parent (widget); - GList *list = gtk_reference_list_accessible_value_get (value); + if ((GTK_IS_TOGGLE_BUTTON (widget) && GTK_IS_DROP_DOWN (parent)) || + (GTK_IS_TOGGLE_BUTTON (widget) && GTK_IS_MENU_BUTTON (parent)) || + (GTK_IS_BUTTON (widget) && GTK_IS_COLOR_DIALOG_BUTTON (parent)) || + (GTK_IS_BUTTON (widget) && GTK_IS_FONT_DIALOG_BUTTON (parent)) +#ifdef G_OS_UNIX + || (GTK_IS_PRINTER_OPTION_WIDGET (parent) && + (GTK_IS_CHECK_BUTTON (widget) || + GTK_IS_DROP_DOWN (widget) || + GTK_IS_ENTRY (widget) || + GTK_IS_IMAGE (widget) || + GTK_IS_LABEL (widget) || + GTK_IS_BUTTON (widget))) +#endif + ) + return TRUE; - for (GList *l = list; l != NULL; l = l->next) - { - GtkAccessible *rel = GTK_ACCESSIBLE (l->data); - GtkATContext *rel_context = gtk_accessible_get_at_context (rel); + return FALSE; +} - gtk_at_context_get_name_accumulate (rel_context, names, FALSE); +static GtkATContext * +get_parent_context (GtkATContext *self) +{ + GtkAccessible *accessible, *parent; - g_object_unref (rel_context); - } + accessible = gtk_at_context_get_accessible (self); + parent = gtk_accessible_get_accessible_parent (accessible); + if (parent) + { + GtkATContext *context = gtk_accessible_get_at_context (parent); + g_object_unref (parent); + return context; } - GtkAccessibleRole role = gtk_at_context_get_accessible_role (self); + return g_object_ref (self); +} - switch ((int) role) +static inline gboolean +not_just_space (const char *text) +{ + for (const char *p = text; *p; p = g_utf8_next_char (p)) { - case GTK_ACCESSIBLE_ROLE_RANGE: - { - int range_attrs[] = { - GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT, - GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, - }; - - value = NULL; - for (int i = 0; i < G_N_ELEMENTS (range_attrs); i++) - { - if (gtk_accessible_attribute_set_contains (self->properties, range_attrs[i])) - { - value = gtk_accessible_attribute_set_get_value (self->properties, range_attrs[i]); - break; - } - } - - if (value != NULL) - g_ptr_array_add (names, (char *) gtk_string_accessible_value_get (value)); - } - break; - - default: - break; + if (!g_unichar_isspace (g_utf8_get_char (p))) + return TRUE; } - /* If there is no label or labelled-by attribute, hidden elements - * have no name - */ - if (gtk_accessible_attribute_set_contains (self->states, GTK_ACCESSIBLE_STATE_HIDDEN)) - { - value = gtk_accessible_attribute_set_get_value (self->states, GTK_ACCESSIBLE_STATE_HIDDEN); - - if (gtk_boolean_accessible_value_get (value)) - return; - } + return FALSE; +} - if (names->len == 0) - { - if (GTK_IS_WIDGET (self->accessible)) - { - const char *tooltip = gtk_widget_get_tooltip_text (GTK_WIDGET (self->accessible)); - if (tooltip) - g_ptr_array_add (names, (char *) tooltip); - } - } +static inline void +append_with_space (GString *str, + const char *text) +{ + if (str->len > 0) + g_string_append (str, " "); + g_string_append (str, text); } +/* See the WAI-ARIA § 4.3, "Accessible Name and Description Computation", + * and https://www.w3.org/TR/accname-1.2/ + */ + static void -gtk_at_context_get_description_accumulate (GtkATContext *self, - GPtrArray *labels, - gboolean recurse) +gtk_at_context_get_text_accumulate (GtkATContext *self, + GPtrArray *nodes, + GString *res, + GtkAccessibleProperty property, + GtkAccessibleRelation relation, + gboolean is_ref, + gboolean is_child) { GtkAccessibleValue *value = NULL; - if (gtk_accessible_attribute_set_contains (self->properties, GTK_ACCESSIBLE_PROPERTY_DESCRIPTION)) + /* Step 2.A */ + if (!is_ref) { - value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_DESCRIPTION); + if (gtk_accessible_attribute_set_contains (self->states, GTK_ACCESSIBLE_STATE_HIDDEN)) + { + value = gtk_accessible_attribute_set_get_value (self->states, GTK_ACCESSIBLE_STATE_HIDDEN); - g_ptr_array_add (labels, (char *) gtk_string_accessible_value_get (value)); + if (gtk_boolean_accessible_value_get (value)) + return; + } } - if (recurse && gtk_accessible_attribute_set_contains (self->relations, GTK_ACCESSIBLE_RELATION_DESCRIBED_BY)) + if (gtk_accessible_role_supports_name_from_author (self->accessible_role)) { - value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_DESCRIBED_BY); + /* Step 2.B */ + if (!is_ref && gtk_accessible_attribute_set_contains (self->relations, relation)) + { + value = gtk_accessible_attribute_set_get_value (self->relations, relation); - GList *list = gtk_reference_list_accessible_value_get (value); + GList *list = gtk_reference_list_accessible_value_get (value); - for (GList *l = list; l != NULL; l = l->next) - { - GtkAccessible *rel = GTK_ACCESSIBLE (l->data); - GtkATContext *rel_context = gtk_accessible_get_at_context (rel); + for (GList *l = list; l != NULL; l = l->next) + { + GtkAccessible *rel = GTK_ACCESSIBLE (l->data); + if (!g_ptr_array_find (nodes, rel, NULL)) + { + GtkATContext *rel_context = gtk_accessible_get_at_context (rel); - gtk_at_context_get_description_accumulate (rel_context, labels, FALSE); + g_ptr_array_add (nodes, rel); + gtk_at_context_get_text_accumulate (rel_context, nodes, res, property, relation, TRUE, FALSE); - g_object_unref (rel_context); + g_object_unref (rel_context); + } + } + + return; } - } - GtkAccessibleRole role = gtk_at_context_get_accessible_role (self); + /* Step 2.C */ + if (gtk_accessible_attribute_set_contains (self->properties, property)) + { + value = gtk_accessible_attribute_set_get_value (self->properties, property); + + char *str = (char *) gtk_string_accessible_value_get (value); + if (str[0] != '\0') + { + append_with_space (res, gtk_string_accessible_value_get (value)); + return; + } + } + } - switch ((int) role) + /* Step 2.E */ + switch ((int) self->accessible_role) { + case GTK_ACCESSIBLE_ROLE_TEXT_BOX: + { + if (GTK_IS_EDITABLE (self->accessible)) + { + const char *text = gtk_editable_get_text (GTK_EDITABLE (self->accessible)); + if (text && not_just_space (text)) + append_with_space (res, text); + } + } + return; + case GTK_ACCESSIBLE_ROLE_RANGE: { int range_attrs[] = { @@ -1283,104 +1313,71 @@ gtk_at_context_get_description_accumulate (GtkATContext *self, } if (value != NULL) - g_ptr_array_add (labels, (char *) gtk_string_accessible_value_get (value)); + append_with_space (res, gtk_string_accessible_value_get (value)); } - break; + return; default: break; } - /* If there is no description or described-by attribute, hidden elements - * have no description - */ - if (gtk_accessible_attribute_set_contains (self->states, GTK_ACCESSIBLE_STATE_HIDDEN)) - { - value = gtk_accessible_attribute_set_get_value (self->states, GTK_ACCESSIBLE_STATE_HIDDEN); - - if (gtk_boolean_accessible_value_get (value)) - return; - } - - if (labels->len == 0) + /* Step 2.F */ + if (gtk_accessible_role_supports_name_from_content (self->accessible_role) || is_ref || is_child) { if (GTK_IS_WIDGET (self->accessible)) { - const char *tooltip = gtk_widget_get_tooltip_text (GTK_WIDGET (self->accessible)); - if (tooltip) - g_ptr_array_add (labels, (char *) tooltip); - } - } -} + gboolean has_child = FALSE; -static gboolean -is_nested_button (GtkATContext *self) -{ - GtkAccessible *accessible; - GtkWidget *widget, *parent; + for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->accessible)); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + GtkAccessible *rel = GTK_ACCESSIBLE (child); + GtkATContext *rel_context = gtk_accessible_get_at_context (rel); - accessible = gtk_at_context_get_accessible (self); + gtk_at_context_get_text_accumulate (rel_context, nodes, res, property, relation, FALSE, TRUE); - if (!GTK_IS_WIDGET (accessible)) - return FALSE; + has_child = TRUE; + } - widget = GTK_WIDGET (accessible); - parent = gtk_widget_get_parent (widget); - - if ((GTK_IS_TOGGLE_BUTTON (widget) && GTK_IS_DROP_DOWN (parent)) || - (GTK_IS_TOGGLE_BUTTON (widget) && GTK_IS_MENU_BUTTON (parent)) || - (GTK_IS_BUTTON (widget) && GTK_IS_COLOR_DIALOG_BUTTON (parent)) || - (GTK_IS_BUTTON (widget) && GTK_IS_FONT_DIALOG_BUTTON (parent)) -#ifdef G_OS_UNIX - || (GTK_IS_PRINTER_OPTION_WIDGET (parent) && - (GTK_IS_CHECK_BUTTON (widget) || - GTK_IS_DROP_DOWN (widget) || - GTK_IS_ENTRY (widget) || - GTK_IS_IMAGE (widget) || - GTK_IS_LABEL (widget) || - GTK_IS_BUTTON (widget))) -#endif - ) - return TRUE; - - return FALSE; -} - -static GtkATContext * -get_parent_context (GtkATContext *self) -{ - GtkAccessible *accessible, *parent; + if (has_child) + return; + } + } - accessible = gtk_at_context_get_accessible (self); - parent = gtk_accessible_get_accessible_parent (accessible); - if (parent) + /* Step 2.G */ + if (GTK_IS_LABEL (self->accessible)) { - GtkATContext *context = gtk_accessible_get_at_context (parent); - g_object_unref (parent); - return context; + const char *text = gtk_label_get_text (GTK_LABEL (self->accessible)); + if (text && not_just_space (text)) + append_with_space (res, text); + return; + } + else if (GTK_IS_INSCRIPTION (self->accessible)) + { + const char *text = gtk_inscription_get_text (GTK_INSCRIPTION (self->accessible)); + if (text && not_just_space (text)) + append_with_space (res, text); + return; } - return g_object_ref (self); + /* Step 2.I */ + if (GTK_IS_WIDGET (self->accessible)) + { + const char *text = gtk_widget_get_tooltip_text (GTK_WIDGET (self->accessible)); + if (text && not_just_space (text)) + append_with_space (res, text); + } } - -/*< private > - * gtk_at_context_get_name: - * @self: a `GtkATContext` - * - * Retrieves the accessible name of the `GtkATContext`. - * - * This is a convenience function meant to be used by `GtkATContext` implementations. - * - * Returns: (transfer full): the label of the `GtkATContext` - */ -char * -gtk_at_context_get_name (GtkATContext *self) +static char * +gtk_at_context_get_text (GtkATContext *self, + GtkAccessibleProperty property, + GtkAccessibleRelation relation) { GtkATContext *parent = NULL; - g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL); - + /* Step 1 */ if (gtk_accessible_role_get_naming (self->accessible_role) == GTK_ACCESSIBLE_NAME_PROHIBITED) return g_strdup (""); @@ -1402,33 +1399,37 @@ gtk_at_context_get_name (GtkATContext *self) } } - GPtrArray *names = g_ptr_array_new (); - - gtk_at_context_get_name_accumulate (self, names, TRUE); - - if (names->len == 0) - { - g_ptr_array_unref (names); - g_clear_object (&parent); - return g_strdup (""); - } - + GPtrArray *nodes = g_ptr_array_new (); GString *res = g_string_new (""); - g_string_append (res, g_ptr_array_index (names, 0)); - for (guint i = 1; i < names->len; i++) - { - g_string_append (res, " "); - g_string_append (res, g_ptr_array_index (names, i)); - } + /* Step 2 */ + gtk_at_context_get_text_accumulate (self, nodes, res, property, relation, FALSE, FALSE); - g_ptr_array_unref (names); + g_ptr_array_unref (nodes); g_clear_object (&parent); return g_string_free (res, FALSE); } +/*< private > + * gtk_at_context_get_name: + * @self: a `GtkATContext` + * + * Retrieves the accessible name of the `GtkATContext`. + * + * This is a convenience function meant to be used by `GtkATContext` implementations. + * + * Returns: (transfer full): the label of the `GtkATContext` + */ +char * +gtk_at_context_get_name (GtkATContext *self) +{ + g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL); + + return gtk_at_context_get_text (self, GTK_ACCESSIBLE_PROPERTY_LABEL, GTK_ACCESSIBLE_RELATION_LABELLED_BY); +} + /*< private > * gtk_at_context_get_description: * @self: a `GtkATContext` @@ -1442,49 +1443,9 @@ gtk_at_context_get_name (GtkATContext *self) char * gtk_at_context_get_description (GtkATContext *self) { - GtkATContext *parent = NULL; - g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL); - if (gtk_accessible_role_get_naming (self->accessible_role) == GTK_ACCESSIBLE_NAME_PROHIBITED) - return g_strdup (""); - - /* We special case this here since it is a common pattern: - * We have a 'wrapper' object, like a GtkDropdown which - * contains a toggle button. The dropdown appears in the - * ui file and carries all the a11y attributes, but the - * focus ends up on the toggle button. - */ - if (is_nested_button (self)) - { - parent = get_parent_context (self); - self = parent; - } - - GPtrArray *names = g_ptr_array_new (); - - gtk_at_context_get_description_accumulate (self, names, TRUE); - - if (names->len == 0) - { - g_ptr_array_unref (names); - g_clear_object (&parent); - return g_strdup (""); - } - - GString *res = g_string_new (""); - g_string_append (res, g_ptr_array_index (names, 0)); - - for (guint i = 1; i < names->len; i++) - { - g_string_append (res, " "); - g_string_append (res, g_ptr_array_index (names, i)); - } - - g_ptr_array_unref (names); - - g_clear_object (&parent); - return g_string_free (res, FALSE); + return gtk_at_context_get_text (self, GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, GTK_ACCESSIBLE_RELATION_DESCRIBED_BY); } void -- 2.30.2